package neptune.manager.allocator;

import java.io.File;

import neptune.manager.NeptuneManager;
import neptune.manager.connector.Connector;
import neptune.manager.connector.ConnectorException;
import neptune.manager.connector.LibvirtXenConnector;
import neptune.manager.model.ApplicationProperties;
import neptune.manager.model.cluster.Cluster;
import neptune.manager.model.cluster.PhysicalNode;
import neptune.manager.model.template.VmTemplate.OsDistributions;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.NotEnoughResourcesException;
import neptune.manager.model.topology.Topology;
import neptune.manager.model.topology.UndefinedControlInterfaceException;
import neptune.manager.storage.StorageReadException;
import neptune.manager.storage.StorageSaveException;
import neptune.manager.storage.XMLApplicationPropertiesStorageFactory;
import neptune.manager.storage.XMLClusterStorageFactory;
import neptune.manager.utils.DebugLog;



public class AsyncExperimentAllocator implements Runnable 
{
	
	private String scriptPath;
	private String vmTemplateSourcePath;
	private String experimentsVMsPath;
	private String expId;
	private Topology topology;
	private Cluster cluster;
	private static int actualMachine = 0;
	private static final String IMG_EXTENSION = ".img";
	private String emuPluginRootPath;
	private String gatewayControlInterface; // il gateway dell'interfaccia di controllo
	
	public AsyncExperimentAllocator(String emuPluginRootPath, String vmTemplatesPath,
			String experimentsVmsPath, String expId, Topology topology, Cluster cluster)throws AllocationException, StorageReadException
	{
		this.emuPluginRootPath=emuPluginRootPath;
		this.vmTemplateSourcePath=vmTemplatesPath;
		this.experimentsVMsPath=experimentsVmsPath;
		this.expId=expId;
		this.topology=topology;
		this.cluster=cluster;
		this.scriptPath = emuPluginRootPath + "scripts/clone.sh";
		String pathConfigFile=emuPluginRootPath+File.separator+"configuration.xml";
		XMLApplicationPropertiesStorageFactory appPropFactory=new XMLApplicationPropertiesStorageFactory(pathConfigFile);
		ApplicationProperties appProperties = appPropFactory.getApplicationPropertiesStorage().getApplicationProperties();
		this.gatewayControlInterface= appProperties.getGatewayControlIpRange();
	}

	
	
	@Override
	public void run()
	{
		if ( this.isRisorseDisponibili() )
		{
			// Aggiungo i task
			String taskId=String.valueOf( System.currentTimeMillis() );
			for (Node node : topology.getNodes()) 
			{
				System.out.println("***->" +  node.getId() );
				String vmName=node.getId();
				Job job=new Job();
				job.setDescription("Deploy of virtual machine "+vmName);
				job.setId(vmName);
				job.setTaskId(taskId);
				JobQueue.getInstance().enqueue(job);
			}
			
			
			DebugLog
			.printDetailedLog("Starting allocation for topology '"
					+ topology.getName() + "'", MainAllocator.class
					.getSimpleName());
			int machineNumber = cluster.getNodes().length;
			for (Node node : topology.getNodes()) 
			{
				DebugLog.printDetailedLog("Allocating node '" + node.getId() + "'",
						MainAllocator.class.getSimpleName());
			
				PhysicalNode machine = cluster.getNodes()[actualMachine];
			
				DebugLog.printDetailedLog("Trying allocation on '"
						+ machine.getId() + "'", MainAllocator.class
						.getSimpleName());
				Job job=null;
				try 
				{
			
					Connector connector = NeptuneManager.instance().getConnector(
							machine.getId());
			
					String[] macAddresses = new String[node.getInterfaces().length];
					for (int i = 0; i < macAddresses.length; i++) 
					{
						macAddresses[i] = node.getInterfaces()[i].getMacAddress()
								.getAddress();
					}
			
					String nodeFullId = node.getId()
							+ NeptuneManager.EXPID_NODEID_SEPARATOR + expId;
			
					int distribution;				
					
					if ( node.getOperativeSystem().getName().equals(OsDistributions.Debian.toString() )  ) 
					{
						distribution = LibvirtXenConnector.DIST_DEBIAN;
					}
					else if( node.getOperativeSystem().getName().equals(OsDistributions.CentOS.toString() )  )
					{
						distribution = LibvirtXenConnector.DIST_CENTOS;
					}
					else if ( node.getOperativeSystem().getName().equals(OsDistributions.Fedora.toString() )  )
					{
						distribution = LibvirtXenConnector.DIST_FEDORA;
					}
					else
					{
						distribution = LibvirtXenConnector.DIST_UNDEFINED;
					}
					DebugLog.printDetailedLog("Connector creation",
							AsyncExperimentAllocator.class.getSimpleName());
					// cambio lo stato del job
					String vmName=node.getId();
					job=JobQueue.getInstance().getJob(taskId, vmName);
					if (job != null)
					{
						job.setState(Job.STATE_RUNNING);
					}
					// creao la macchina virtuale
					connector.createVM(nodeFullId, node.getControlAddress().getAddress(),
							node.getControlAddress().getSubnetMask(),
							this.gatewayControlInterface, macAddresses,
							vmTemplateSourcePath + node.getSoftwareTemplate()
									+ IMG_EXTENSION, experimentsVMsPath
									+ nodeFullId + IMG_EXTENSION, distribution,
							node.getRam(), scriptPath, "/sbin", "/sbin", null);
					connector.closeConnection();
					// cambio lo stato del job
					if (job != null)
					{
						job.setState(Job.STATE_FINISH);
					}
					machine.addHostedNode(node, topology.getName());
					actualMachine++;
					DebugLog.printDetailedLog("Allocated", MainAllocator.class
							.getSimpleName());
					if (actualMachine == machineNumber)
						actualMachine = 0;
					// aggiorno il file cluster.xml
					String pathFileCluster=this.emuPluginRootPath+File.separator+"cluster.xml";
					XMLClusterStorageFactory clusterFactory=new XMLClusterStorageFactory(pathFileCluster);
					clusterFactory.getClusterStorage().saveCluster(this.cluster);
				}
				catch (NotEnoughResourcesException e) 
				{
					if (job != null)
					{
						job.setState(Job.STATE_FAILED);
					}
					e.printStackTrace();
					DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
							.getSimpleName());
					throw new RuntimeException(e);
				}
				catch (ConnectorException e)
				{
					if (job != null)
					{
						job.setState(Job.STATE_FAILED);
					}
					DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
							.getSimpleName());
					//throw new AllocationException(e);
					throw new RuntimeException(e);
				} 
				catch (UndefinedControlInterfaceException e) 
				{
					if (job != null)
					{
						job.setState(Job.STATE_FAILED);
					}
					DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
							.getSimpleName());
					throw new RuntimeException(e);
				} 
				catch (StorageSaveException e) 
				{
					if (job != null)
					{
						job.setState(Job.STATE_FAILED);
					}
					DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
							.getSimpleName());
					throw new RuntimeException(e);
				}
			}
		}
		else
		{
			// Non ci sono risorse disponibili per allocare l'esperimento
		}
		
	}
	
	private boolean isRisorseDisponibili()
	{
		if (this.cluster.getNodes().length== 0 )
		{
			return false;
		}
		return true;
	}

	
}
